package com.starview.platform.resource;

import com.starview.platform.data.value.ResourceURI;
import com.starview.star.StarRules;
import com.starview.star.compiler.util.FileUtil;
import com.starview.star.compiler.util.StringUtils;

import java.io.*;
import java.security.MessageDigest;
import java.util.HashMap;
import java.util.Map;

public class Resources{
  public static final String FILE = "file";
  public static final String STDSCHEME = "std";
  public static final String QSCHEME = "$quoted$";
  public static final String HTTP = "http";
  public static final String STAR = "star";

  private static final Map<String, Transducer> transducers = new HashMap<String, Transducer>();

  static{
    transducers.put(FILE, new FileTransducer());
    transducers.put(STDSCHEME, new StdTransducer());
    transducers.put(QSCHEME, new QuoteTransducer());
    transducers.put(HTTP, new HttpTransducer());
  }

  public static Reader getReader(ResourceURI uri) throws ResourceException{
    Transducer transducer = transducers.get(uri.getScheme());
    if (transducer != null)
      return transducer.getReader(uri);
    else
      throw new ResourceException("cannot find transducer for " + uri);
  }

  public static InputStream getInputStream(ResourceURI uri) throws ResourceException{
    Transducer transducer = transducers.get(uri.getScheme());
    if (transducer != null)
      return transducer.getInputStream(uri);
    else
      throw new ResourceException("cannot find transducer for " + uri);
  }

  public static String getUriContent(ResourceURI uri) throws ResourceException{
    Reader rdr = getReader(uri);
    try {
      return StringUtils.readFileIntoString(rdr);
    } catch (IOException e) {
      throw new ResourceException(e.getMessage());
    }
  }

  public static boolean exists(ResourceURI uri) throws ResourceException{
    Transducer transducer = transducers.get(uri.getScheme());
    if (transducer != null)
      return transducer.exists(uri);
    else
      throw new ResourceException("cannot find transducer for " + uri);
  }

  public static void putResource(ResourceURI uri, Object obj) throws ResourceException{
    Transducer transducer = transducers.get(uri.getScheme());
    if (transducer != null)
      transducer.putResource(uri, obj);
    else
      throw new ResourceException("cannot find transducer for " + uri);
  }

  public static byte[] getUriData(ResourceURI uri) throws ResourceException{
    InputStream str = getInputStream(uri);
    try {
      return FileUtil.readFileIntoBytes(str);
    } catch (IOException e) {
      throw new ResourceException("could not read bytes from " + uri, e);
    } finally {
      try {
        str.close();
      } catch (IOException e) {
      }
    }
  }

  public static void recordTransducer(String scheme, Transducer transducer){
    transducers.put(scheme, transducer);
  }

  public static class JarTransducer implements Transducer{
    private final Class<?> rootClass;

    public JarTransducer(Class<?> rootClass){
      this.rootClass = rootClass;
    }

    @Override
    public Reader getReader(ResourceURI uri) throws ResourceException{
      String fullResourceName = uri.getPath();
      InputStream istream = rootClass.getResourceAsStream(fullResourceName);

      if (istream == null && !fullResourceName.startsWith("/")) {
        fullResourceName = "/" + fullResourceName;
        istream = StarRules.class.getResourceAsStream(fullResourceName);
      }
      if (istream != null)
        return new InputStreamReader(istream);
      else
        throw new ResourceException(uri + " not accessible");
    }

    @Override
    public InputStream getInputStream(ResourceURI uri) throws ResourceException{
      String fullResourceName = uri.getPath();
      InputStream istream = rootClass.getResourceAsStream(fullResourceName);

      if (istream == null && !fullResourceName.startsWith("/")) {
        fullResourceName = "/" + fullResourceName;
        istream = StarRules.class.getResourceAsStream(fullResourceName);
      }
      if (istream != null)
        return istream;
      else
        throw new ResourceException(uri + " not accessible");
    }

    @Override
    public void putResource(ResourceURI uri, Object resource) throws ResourceException{
      throw new ResourceException("not permitted");
    }

    @Override
    public boolean exists(ResourceURI uri) throws ResourceException{
      String path = uri.getPath();
      return rootClass.getResource(path) != null;
    }
  }

  private static class QuoteTransducer implements Transducer{
    @Override
    public Reader getReader(ResourceURI uri) throws ResourceException{
      String fragment = uri.getFragment();
      if (fragment == null)
        throw new ResourceException("QuoteTransducer cannot find fragment in " + uri);
      return new StringReader(fragment);
    }

    @Override
    public InputStream getInputStream(ResourceURI uri) throws ResourceException{
      String fragment = uri.getFragment();
      if (fragment == null)
        throw new ResourceException("QuoteTransducer cannot find fragment in " + uri);
      return new ByteArrayInputStream(fragment.getBytes());
    }

    @Override
    public void putResource(ResourceURI uri, Object resource) throws ResourceException{
      throw new ResourceException("not permitted");
    }

    @Override
    public boolean exists(ResourceURI uri) throws ResourceException{
      return uri.getFragment() != null;
    }
  }

  public static String resourceHash(ResourceURI uri) throws ResourceException{
    InputStream rdr = getInputStream(uri);
    try {
      MessageDigest md5 = MessageDigest.getInstance("MD5");
      byte chBuff[] = new byte[1024];

      for (int len = rdr.read(chBuff); len > 0; len = rdr.read(chBuff))
        md5.update(chBuff, 0, len);

      byte[] digest = md5.digest();

      return StringUtils.hexString(digest);
    } catch (Exception e) {
      throw new ResourceException("cannot compute hash of " + uri, e);
    } finally {
      try {
        rdr.close();
      } catch (Exception e) {
      }
    }
  }
}
